<template>
  <article class="page page-jssfres-download">
    <h4 class="text-center">京师书法课件资源下载器(旧)</h4>
    <form class="form">
      <div class="alert alert-info">
        资源下载完成后会生成一个以“课本ID”命名的文件夹，使用时，需先将该文件夹改名为“coursewares”，然后拷贝到京师书法程序目录下的“resources”文件夹中即可。
      </div>
      <div class="form-group">
        <fieldset>
          <legend>参数设置</legend>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">课本ID</div>
            <div class="row-flex-main"><input class="form-control" type="text" v-model="bookId" /></div>
            <div class="row-flex-suffix">
              <x-button :loading="loadingCount > 0" @click="downloadHandler">下载</x-button>
            </div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">授课模式</div>
            <div class="row-flex-main text-left">
              <div class="form-check form-check-inline" v-for="(v, i) in teachModeList" :key="i">
                <input class="form-check-input" type="radio" :value="v.value" v-model="teachMode">
                <label class="form-check-label">{{ v.label }}</label>
              </div>
            </div>
            <div class="row-flex-suffix" style="width: 8em;"></div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">Token</div>
            <div class="row-flex-main"><input class="form-control" type="text" v-model="token" :disabled="tokenLocked" /></div>
            <div class="row-flex-suffix">
              <x-button @click="tokenLocked=!tokenLocked">{{tokenLocked ? '解锁' : '锁定'}}</x-button>
            </div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">保存位置</div>
            <div class="row-flex-main"><input class="form-control" type="text" v-model="toPath" /></div>
            <div class="row-flex-suffix">
              <x-button :loading="loadingCount > 0" @click="setToPath">选择...</x-button>
            </div>
          </div>
        </fieldset>
      </div>
      <div class="form-group form-group-log">
        <fieldset>
          <legend>打印日志</legend>
          <div class="fieldset-content">
            <textarea class="form-control" v-model.trim="result" rows="10" readonly></textarea>
          </div>
        </fieldset>
      </div>
    </form>
  </article>
</template>

<script>
const fs = global.require('fs')
const path = global.require('path')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()
let JSZip = global.JSZip || null
let {ajax, ajaxJson, ajaxError, downloadFileV3, getJson, readDir, delFile, writeFileSync, unzipFile} = global.jstoolsNode || {}
let _url = {
  getBookCoursewaresList: 'http://gw.jsxfedu.com/sf-server/courseware/list-book-courseware', // 查询课件列表
  getBatchFileUrl: 'http://gw.jsxfedu.com/sf-base/oss/post-batch-file-url', // 批量获取文件下载地址
  getChapterCourseware: 'https://gw.jsxfedu.com/sf-server/book/chapter-courseware', // 获取课本章节及课件
}
let _key2url = {}
let _downloadKeys = [] // 临时存储的资源Key
let _encryptFiles = [] // 待加密文件列表
let _zipLog = {} // 日志

export default {
  name: 'jssfres-download',
  data () {
    return {
      bookId: '',
      teachMode: 'PROCESS',
      teachModeList: [
        {value: 'PROCESS', label: '流程模式'},
        {value: 'CONCISE', label: '精简模式'}
      ], // 可用的授课模式列表
      token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzZiIsImF1ZCI6IlktMSIsImlzcyI6InNmIiwiZXhwIjo0MTAyNDE1OTk5fQ.zZ-4gQ7Ty5xi3ap1TfWeNuMef_wVo-IH-lBn3evfMKc',
      tokenLocked: true,
      toPath: this.$sharedObject('desktopPath'),
      result: '',
      bookCoursewaresList: [],
      chapterCourseware: {},
      encryptPassword: 'l6fwZHdJbU2Y4jxPDwjoI6P9vltHhc8bvDlExm4vRRg=',
      loadingCount: 0
    }
  },
  methods: {
    // 设置保存位置
    setToPath () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择目录',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openDirectory']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.toPath = filePaths[0]
      }
    },
    // 获取课本章节及课件
    getChapterCourseware () {
      this.chapterCourseware = {}
      return ajax.get(_url.getChapterCourseware, {
        params: {bookId: this.bookId, teachingMode: this.teachMode},
        headers: {token: this.token}
      }).then(res => {
        let data = this.ajaxCheck(res)
        this.chapterCourseware = data || {}
      }).catch(err => {
        let msg = ajaxError(err)
        this.result += msg + '\n'
      })
    },
    // 获取章节列表
    getBookCoursewaresList () {
      this.bookCoursewaresList = []
      return ajax.get(_url.getBookCoursewaresList, {
        params: {bookId: this.bookId, teachingMode: this.teachMode},
        headers: {token: this.token}
      }).then(res => {
        let data = this.ajaxCheck(res)
        this.bookCoursewaresList = data || []
        this.result += `查询到 ${this.bookCoursewaresList.length} 个课件` + '\n'
      }).catch(err => {
        let msg = ajaxError(err)
        this.result += msg + '\n'
      })
    },
    // 获取章节列表--批量获取文件下载地址
    getBookCoursewaresUrl () {
      _downloadKeys = []
      if (this.bookCoursewaresList.length === 0) {
        return Promise.resolve()
      }
      this.bookCoursewaresList.forEach(v => {
        v.fileKey && _downloadKeys.push(v.fileKey)
        v.videoKey && _downloadKeys.push(v.videoKey)
      })
      return this.resourceInfoGetter(_downloadKeys)
    },
    // 获取章节列表--下载课件和微课
    downloadBookCoursewaresUrl () {
      let _this = this
      let _downloadTasks = []
      _downloadKeys.forEach(v => {
        let coursewareName = path.basename(v || '')
        let coursewarePath = path.join(this.toPath, this.bookId, coursewareName)
        _downloadTasks.push({from: _key2url[v], to: coursewarePath, name: coursewareName})
      })
      return new Promise((resolve) => {
        if (_downloadTasks.length === 0) { resolve() }
        downloadRecursion(0)
        // 下载·递归
        function downloadRecursion (index) {
          downloadFileV3(_downloadTasks[index]).then(() => {
            _this.result += `下载(${index + 1}) ${_downloadTasks[index].name} 成功` + '\n'
          }).catch(() => {
            _this.result += `下载(${index + 1}) ${_downloadTasks[index].name} 失败` + '\n'
          }).finally(() => {
            if (index < (_downloadTasks.length - 1)) {
              downloadRecursion(index + 1)
            } else { resolve()}
          })
        }
      })
    },
    // 获取课件中索引的远程资源Key列表
    getBookCoursewaresResKey () {
      let _this = this
      let _unzipTasks = []
      _downloadKeys.forEach(v => {
        if (!/zip_encrypted$/.test(v)) return
        let coursewareName = path.basename(v || '')
        let coursewarePath = path.join(this.toPath, this.bookId, coursewareName)
        let _coursewareName = coursewareName.replace('.zip_encrypted', '')
        let _dirPath = path.join(this.toPath, this.bookId)
        let _unzipPath = path.join(this.toPath, this.bookId, _coursewareName)
        _unzipTasks.push({name: _coursewareName, path: coursewarePath, dirPath: _dirPath, unzipPath: _unzipPath})
      })
      return new Promise((resolve) => {
        if (_unzipTasks.length === 0) { resolve() }
        let encryptKey = Buffer.from(this.encryptPassword, 'base64')
        _downloadKeys = []
        unzipRecursion(0)
        // 解压·递归
        function unzipRecursion (index) {
          let _item = _unzipTasks[index]
          unzipFile({
            from: _item.path, to: _item.dirPath, JSZip, encryptKey
          }).then(async() => {
            let _pptData = await getJson(path.join(_item.unzipPath, 'pptData.json')) || {}
            let _pagesList = Object.values(_pptData.pageInfo) || []
            _pagesList.forEach(v => {
              let tts = v.tts || {}
              v.videoKey && _downloadKeys.push(v.videoKey)
              tts.key && _downloadKeys.push(tts.key)
            })
            _this.result += `解压(${index + 1}) ${_item.name} 成功` + '\n'
            if (index < (_unzipTasks.length - 1)) {
              unzipRecursion(index + 1)
            } else { resolve()}
          }).catch(() => {
            _this.result += `解压(${index + 1}) ${_item.name} 失败` + '\n'
            if (index < (_unzipTasks.length - 1)) {
              unzipRecursion(index + 1)
            } else { resolve()}
          })
        }
      })
    },
    // 获取待加密的文件列表
    getEncryptFilesList() {
      _encryptFiles = []
      _zipLog = {}
      let coursewaresDir = path.join(this.toPath, this.bookId)
      return readDir(coursewaresDir).then((files) => {
        files.forEach((file) => {
          let fp = path.join(coursewaresDir, file)
          let fpStat = fs.lstatSync(fp)
          // 删除解压的课件包
          if (fpStat.isDirectory()) { delFile(fp) }
          else if (/zip_encrypted$/.test(file)) {
            _zipLog[file] = {updateTime: Date.now()}
          } else {
            _encryptFiles.push({name: file, path: fp})
          }
        })
      }).catch(() => {
        this.result += `获取待加密的文件列表失败` + '\n'
      })
    },
    // 资源加密
    encrypt() {
      let _this = this
      return new Promise((resolve) => {
        if (_encryptFiles.length === 0) { resolve() }
        encryptRecursion(0)
        // 加密·递归
        function encryptRecursion (index) {
          _this.encryptItem(_encryptFiles[index]).then(() => {
            _this.result += `加密(${index + 1}) ${_encryptFiles[index].name} 成功` + '\n'
          }).catch(() => {
            _this.result += `加密(${index + 1}) ${_encryptFiles[index].name} 失败` + '\n'
          }).finally(() => {
            if (index < (_encryptFiles.length - 1)) {
              encryptRecursion(index + 1)
            } else { resolve()}
          })
        }
      })
    },
    encryptItem (item) {
      return new Promise((resolve, reject) => {
        fs.readFile(item.path, (err, data) => {
          if (err) {
            this.result += `读取文件失败: ${item.name}` + '\n'
            reject()
          } else {
            let encrypted = Buffer.from(data)
            for (let i = 0; i < 10; i++) {
              encrypted[i] = encrypted[i] ^ 0xFB
            }
            writeFileSync(item.path, encrypted)
            resolve()
          }
        })
      })
    },
    // 根据资源Key获取Url
    resourceInfoGetter (fileNames) {
      fileNames = fileNames || []
      if (fileNames.length === 0) return Promise.resolve([])
      return ajaxJson.post(_url.getBatchFileUrl, {
        expiration: 36000,
        fileNames: fileNames
      }, {
        headers: {token: this.token}
      }).then(res => {
        let data = this.ajaxCheck(res)
        if (!data || !(data instanceof Array)) {
          this.result += '接口响应数据为空' + '\n'
        } else {
          data.forEach((v, i) => { _key2url[fileNames[i]] = v })
        }
        return data
      }).catch(err => {
        let msg = ajaxError(err)
        this.result += msg + '\n'
      })
    },
    // 保存zipLog
    writeZipLog () {
      let _toPath = path.join(this.toPath, this.bookId, 'log.json')
      writeFileSync(_toPath, JSON.stringify(_zipLog))
      _zipLog = {}
    },
    // 生成base.data
    writeBaseData () {
      let _toPath = path.join(this.toPath, this.bookId, 'base.data')
      let _dataStr = JSON.stringify(this.chapterCourseware)
      let _dataBuf = Buffer.from(_dataStr)
      let _dataBufLen = _dataBuf.length
      for (let i = 0; i < _dataBufLen; i++) {
        _dataBuf[i] = _dataBuf[i] ^ 0xFB
      }
      writeFileSync(_toPath, _dataBuf)
    },
    // 响应数据预处理
    ajaxCheck (response) {
      response.data = response.data || {}
      if (response.status !== 200) {
        this.result += response.statusText + '\n'
        return ''
      }
      if (response.data.code !== 0) {
        this.result += response.data.msg + '\n'
        return ''
      }
      return response.data.data
    },
    // 表单校验
    formCheck () {
      let result = true
      if (!this.bookId) {
        this.result += '课本ID不能为空' + '\n'
        result = false
      }
      if (!this.token) {
        this.result += 'Token不能为空' + '\n'
        result = false
      }
      return result
    },
    async downloadHandler () {
      this.result = ''
      if (!this.formCheck()) return
      this.result = '开始下载...' + '\n'
      this.loadingCount++
      this.result += '开始获取章节课件列表...' + '\n'
      await this.getBookCoursewaresList()
      this.result += '开始获取课件和微课的下载地址...' + '\n'
      await this.getBookCoursewaresUrl()
      this.result += `开始下载课件和微课(共计${_downloadKeys.length}个文件)...` + '\n'
      await this.downloadBookCoursewaresUrl()
      this.result += '开始获取课件包内部远程资源列表...' + '\n'
      await this.getBookCoursewaresResKey()
      this.result += '开始获取课件包内部远程资源的下载地址...' + '\n'
      await this.resourceInfoGetter(_downloadKeys)
      this.result += `开始下载课件包内部远程资源(共计${_downloadKeys.length}个文件)...` + '\n'
      await this.downloadBookCoursewaresUrl()
      this.result += '下载完成' + '\n'
      this.result += '获取待加密的文件列表...' + '\n'
      await this.getEncryptFilesList()
      this.result += '开始加密...' + '\n'
      await this.encrypt()
      this.result += '加密完成' + '\n'
      this.result += '生成课件包日志文件...' + '\n'
      this.writeZipLog()
      this.result += '生成完成' + '\n'
      this.result += '获取课本章节及课件...' + '\n'
      await this.getChapterCourseware()
      this.result += '生成课本基础文件...' + '\n'
      this.writeBaseData()
      this.result += '生成完成' + '\n'
      this.loadingCount--
    }
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
.row-flex {
  & + .row-flex {
    margin-top: .5em;
  }
  .row-flex-prefix {
    width: 6em;
    text-align: right;
  }
}
.form {
  height: calc(100vh - 137px);
  display: flex;
  flex-direction: column;
  .form-group-log {
    margin-top: .5em;
    height: 0;
    flex-grow: 1;
    fieldset {
      height: 100%;
      .fieldset-content {
        height: calc(100% - 2em);
        textarea {
          height: 100%;
        }
      }
    }
  }
  .x-button {
    min-width: 8em;
  }
}
</style>
